//--------------------------------------------------------------------------------
/*!
\file	XMLDoc.h
\author	Ben Smith
\date	09/12/2006
\brief	This file contains the XMLDoc class which is used for XML IO.
\par	license
All content (c) 2005, DigiPen (USA) Corporation, all rights reserved.
*/
//--------------------------------------------------------------------------------

#ifndef XMLDOC_H
#define XMLDOC_H

#include <boost/shared_ptr.hpp>
#include <sstream>
#include <string>
#include <map>

#define XMLDOC_MAX_DATA_SIZE	4096

//POUND DEFINES FOR USABILITY////////////
#define XML_NODE(iter) (*(iter->second))
#define XML_NPTR(iter) (iter->second)
#define XML_NAME(iter) (iter->first)
/////////////////////////////////////////

//Type Definitions////////////////////////////////////////
typedef boost::shared_ptr<std::string>				xmldataptr_t;
typedef std::pair<std::string, std::string>			xmlattrib_t;
typedef boost::shared_ptr<xmlattrib_t>				xmlattribptr_t;
typedef std::map<std::string, xmlattribptr_t>		xmlattribmap_t;
typedef boost::shared_ptr<class XMLNode>			xmlnodeptr_t;
typedef boost::shared_ptr<class XMLTree>			xmltreeptr_t;
typedef std::multimap<std::string, xmlnodeptr_t>	xmlnodemap_t;
typedef xmlnodemap_t::const_iterator				xmlconstiter_t;
typedef xmlnodemap_t::iterator						xmliter_t;
typedef std::pair<xmliter_t,xmliter_t>				xmlrange_t;
typedef std::pair<xmlconstiter_t, xmlconstiter_t>	xmlconstrange_t;
//////////////////////////////////////////////////////////

//--------------------------------------------------------------------------------
/*!
\class	XMLNode
\brief	Holds text data (but not required to) along with a variable number of
		attribute pairs (in form: string='value').  Each node requires a string
		name.  Child nodes can be added to the XMLNode by using the Link command.
*/
//--------------------------------------------------------------------------------
class XMLNode 
{
private:
	std::string							name_;		//!< Name of node for file output
	xmlnodemap_t						children_;	//!< MultiMap of child nodes
	xmlattribmap_t						attributes_;//!< Map of unique attribute pairs
	xmldataptr_t						pData_;		//!< String data (Can be NULL)
public:
	// Constructors
	XMLNode() {}
	XMLNode(const std::string& name, xmldataptr_t pData = xmldataptr_t());
	XMLNode(const std::string& name, const std::string& data);

	// Destructor
	~XMLNode();

	// Read Methods
	const xmldataptr_t GetData() const;
	template<typename T>
	  T GetAttribute(const std::string& tag, const T& defaultVal) const; 
    template<>
	  std::string GetAttribute(const std::string& tag, const std::string& defaultVal) const;

	// Write Methods
	template<typename T>
	  void AddAttribute(const std::string &attribName, const T& attribValue);
    void AddAttribute(const xmlattrib_t& attrib);
	void SetData(xmldataptr_t pData);
	void SetName(const std::string &name)
		{ name_ = name; }
	void Link(const xmlnodeptr_t pNode);
	void Link(const xmliter_t& xmlIter);

	// Get Methods
	const std::string& GetName() const
		{ return name_; }
	const xmlnodemap_t GetChildren() const
		{ return children_; }

	// File I/O Methods
	friend void WriteNode(std::ostream &os, const xmlnodeptr_t pNode, unsigned numTabs=0);
	friend bool ReadNode(std::istream &is, xmlnodeptr_t pNode);
};

//--------------------------------------------------------------------------------
/*!
\class	XMLTree
\brief	XML DOM (Data Object Model).  Each XMLTree represents part of the grand
		data tree, but it only functional on one level at a time.  In order to
		recurse down the tree, a new tree must be created from a node's children,
		or the branch command must be used.

		How To Use::
			In order to find a specific node in the tree, use the Find method:
				xmlrange_t range = Find("MyNodeTitle");
			The return value of this function is a range of nodes (represented
			by a pair of iterators).  This is becuase node names are not required
			to be unique.

			You can also iterate through all nodes on the current tree level by
			grabbing the iterators from Begin() and End():
				for (xmliter_t iter = tree.Begin(); iter != tree.End(); ++iter) {
					//Everything!
				}
			
			To move down into a specific node's subtree, you must either build a
			new tree or use the branch command.
			Note:: Using the banch command will move the entire XMLTree structure
			down one level without a way to get back.  As such, it is probably a
			good idea to create a new temporary tree like so:
				xmlrange_t range = Find("Enemy");
				for (xmliter_t iter = range.first; iter != range.second; ++iter) {
					XMLTree newTree(iter);
					//Now we have a new tree that we can search through and read
				}
*/
//--------------------------------------------------------------------------------
class XMLTree 
{
private:
	bool			ownsNodes_;	//!< Does the tree own the nodes?  Should it destroy them on destruction?
	xmlnodemap_t	tree_;		//!< MultiMap of nodes that represent the top level of the tree
private:
	void TreeFromNode(const xmlnodeptr_t pNode);
public:
	// Constructors
	XMLTree();
	XMLTree(const xmlnodeptr_t pNode);
	XMLTree(const xmliter_t& xmlIter);

	// Destructor
	~XMLTree();

	// Management methods
	void Reset();

	// Traversal methods
	xmlconstrange_t	Find(const std::string& tag) const;
	xmlrange_t		Find(const std::string& tag);
	xmliter_t		Begin();
	xmliter_t		End();
	xmlconstiter_t	Begin() const;
	xmlconstiter_t	End() const;
	XMLTree&		Branch(const xmliter_t& xmlIter);
	XMLTree&		Branch(const xmlnodeptr_t pNode);
	XMLTree&		Branch(const std::string& tag, bool* errorCheck = NULL);
	//bool			Branch(const std::string& tag);

	// Validity methods
	bool ValidRange(const xmlrange_t& range) const
	{ 
		if (range.first == tree_.end() && range.second == tree_.end()) 
			return false; 
		return true; 
	}

	// Write methods
	void Link(const xmltreeptr_t pTree);
	void Link(const xmlnodeptr_t pNode);
	void Link(const xmliter_t& xmlIter);

	// Get functions
	const size_t Size() const { return tree_.size(); }
};

//--------------------------------------------------------------------------------
/*!
\class	XMLDoc
\brief	The XMLDoc represents the entire XML Document.  This file can be opened
		for both reading and writing.  If the constructor is called with the
		fileName, the document will attempt to parse by default.  Upon parsing,
		an XML Data Object Model will be created in the form of a tree.  The meat
		of read/write functionality is expressed in the XmlTree structure.  This
		tree can be accessed with the Get/SetTree methods.
		Note:: Upon parsing an XMLDoc, all comments will be discarded (not added
			   as nodes into the tree).
*/
//--------------------------------------------------------------------------------
class XMLDoc 
{
private:
	std::string		name_;		//!< Name of the XML Document
	std::string		fileName_;	//!< Filename of XML Document for file I/O
	xmltreeptr_t	xmlTree_;	//!< XMLTree Data Model
public:
	// Constructors
	XMLDoc(const std::string& name);
	XMLDoc(const std::string& name, const std::string& fileName);

	// Read Methods
	XMLTree GetTree(bool rootLevel = false) const;

	// Write Methods
	void SetTree(const xmltreeptr_t pTree) 
	  { xmlTree_ = pTree; }

	// File I/O Methods
	void Write(const std::string& fileName);
	void Parse(const std::string& fileName);
};


//--------------------------------------------------------------------------------
/*!
\brief	Adds a new attribute (name-value pair) to the node's attribute map.  Uses
		a stringstream so as to handle any type of value input.
\author	Ben Smith
\param attribName
	String name of the attribute.
\param attribValue
	Templatized attribute value.
*/
//--------------------------------------------------------------------------------
template<typename T>
void XMLNode::AddAttribute(const std::string &attribName, const T& attribValue)
{
	//Create a string stream to take any type to a string
	std::stringstream str;
	str << attribValue;
	attributes_[attribName] = xmlattribptr_t(new xmlattrib_t(attribName, str.str()));
}

//--------------------------------------------------------------------------------
/*!
\brief	Given a string attribute tag, searches for an attribute with the same name
		in the attribute map.  Note:: Case sensitive!
\author	Ben Smith
\param attribName
	String name of the attribute to retrieve.
\return
	A pointer to the attribute value if it could be found.  Otherwise, returns NULL.
*/
//--------------------------------------------------------------------------------
template<typename T>
T XMLNode::GetAttribute(const std::string& tag, const T& defaultVal) const
{
	xmlattribmap_t::const_iterator findIter = attributes_.find(tag);
	if (findIter != attributes_.end()) {
		std::stringstream str;
		T value;
		str.str(findIter->second->second);
		str >> value;
		return value;
	}
	else
		return defaultVal;
}

template<>
std::string XMLNode::GetAttribute(const std::string& tag, const std::string& defaultVal) const
{
	xmlattribmap_t::const_iterator findIter = attributes_.find(tag);
	if (findIter != attributes_.end()) {
		std::stringstream str;
		std::string value;
		str.str(findIter->second->second);
		value = str.str();
		return value;
	}
	else
		return defaultVal;
}

/* ---------------------------------------------------
  HOW TO USE::
------------------------------------------------------
Sample XML file (test.xml):
	<Test>
		<Data>
			<Map>
				<House loc="100" size="5" />
				<House loc="110" size="6" />
				<Bush  loc="80"  size="3" />
			</Map>
			<Config>
				<Audio volume="10" />
				<Audio volume="12" />
				<Intro> Welcome to Gigoon </Intro>
			</Config>
		</Data>
	</Test>

Reading from an xml file:
  1) Create a new XMLDoc object (give it a name)
		XMLDoc xml("myXML");
  2) Call the Parse method and pass in the XML file's
	 name and relative path from the working directory
		xml.Parse("XML\test.xml");
  3) Get a copy of the data tree with the GetTree
	 method. If you pass in true as a parameter then
	 you will have to navigate through the 1 XML root
	 node (usually a waste).
		XMLTree tree = xml.GetTree();
		(In the example, the tree will start out looking
		 at the <Data> node.
  4) Now that you have a tree to work with, realize that
	 you are only looking at one level of the tree at a
	 time.  In the example xml file, we are looking at
	 the level with the <Test> node on it.  This is the
	 only node, so we should move down the tree a bit.
	 There are 2 ways to do this::
		A) Branching:
			The XMLTree has a method called Branch.  You
			can call this method with a node pointer, an
			XML iterator, or even a string node
			identifier.  The branch method will redirect
			current tree to a new level.  Note that there
			is not a good way to move back upwards.
		B) Make a new Tree:
			You can make a whole new tree by passing in a
			pointer to a node or an XML iterator.  This
			will allow you to save the previous tree in
			case you ever need it again.
	 In our example, Data is the only node that we need to
	 look in, so we'll branch.  Also, becuase we know the 
	 name of the node to branch into, we can just use a
     string.  Also note that the Branch method returns
	 a reference to itself, so the calls can be "chained".
		tree.Branch("Data").Branch("Config");
  5) Now that we're on a level with some data nodes, we
     can try to grab some information.  Find specific
	 nodes by using the Find method.  This will return
	 a range (pair of iterators) that will define the
	 found nodes (as there can be more than one with the
	 same name).  The tree's ValidRange method will be
	 able to check whether the returned range contains
	 any valid iterators but is optional.
		xmlrange_t range = tree.Find("Audio");
		if (tree.ValidRange(range)) {
  6) Now we can either check the first iterator if we
     know there is only one node we want, or loop
	 through the range.
		for (xmliter_t iter = range.first ;iter != range.second; ++range.first) {
  7) Use the pound defines for easy node access from
     xml iterators:
			xmlnodeptr_t pNode = XML_NPTR(iter);
  8) Finally, we can get attributes or data from our node.
			unsigned vol = pNode->GetAttribute("volume", 0);
			xmldataptr_t data = pNode->GetData();
  9) Also, notice that you can get iterators to the
     beginning and end of a tree level with the Begin()
	 and End() methods.
 10) Read documentation for more information.  There's
	 a good amount you can do.  You can also create your
	 own XML files with the same interface.
------------------------------------------------------ */

#endif